import sys
import contextlib
from cStringIO import StringIO
from pprint import pprint #@UnusedImport
from pyson.types import is_object, is_array

__all__ = ['indent', 'pprint', 'srepr', 'sprint', 'simpleprint', 'str_itemize', 'printiter']

def indent(st, indent=4, firstline=True, whitespace=' '):
    '''Indent string using ``indent`` whitespaces.
    
    Parameters
    ----------
    st : str or unicode
        String to be indented.
    indent : int
        Number of whitespace characters to be prepended to each line.
    firstline : bool
        If False, the first line will not be indented.
    whitespace : str
        A string representing the whitespace character. In most cases, it will
        be a space (' ') or a tab ('\t'), but any character is allowed.
        
    Examples
    --------
    
    >>> print(indent('foo:\\n--bar', whitespace='-'))
    ----foo:
    ------bar
    '''

    indent = whitespace * indent
    lines = st.splitlines()

    if firstline:
        return '\n'.join(indent + l for l in lines)
    else:
        return (indent + '\n').join(lines)

def str_itemize(strlist, bullet='* '):
    '''Format a list of strings using bullets to mark each item.
    
    Examples
    --------
    
    >>> print(str_itemize(['foo', 'bar', 'spam\\neggs']))
    * foo
    * bar
    * spam
      eggs
    '''
    lines = []
    ws = ' ' * len(bullet)
    for item in strlist:
        for idx, l in enumerate(item.splitlines()):
            if idx:
                lines.append(ws + l)
            else:
                lines.append(bullet + l)
    return '\n'.join(lines)

#===============================================================================
# Visualization functions
#===============================================================================
_indent_f = indent # to use inside srepr implementation
def srepr(obj, indent=4, bullet='* '):
    '''
    Display JSON-like objects in a compact way. Differently to pretty print, it
    does not format objects using a valid dict/list syntax.
    
    Examples
    --------
    
    >>> obj = {'foo': {'foobar': 1, 'bar': 'null'}, 'bar': ['null', 1, 2, 3]} 
    >>> print(srepr(obj))
    bar:
        * null
        * 1
        * 2
        * 3
    foo:
        bar: null
        foobar: 1
    '''
    if is_object(obj):
        if obj:
            items = []
            obj_items = obj.items()
            obj_items.sort()
            for key, value in obj_items:
                if is_object(value) or is_array(value):
                    value = '\n' + _indent_f(srepr(value), indent)
                else:
                    value = ' ' + srepr(value)
                items.append('%s:%s' % (key, value))
            return '\n'.join(items)
        return '<empty>'
    elif is_array(obj):
        if obj:
            return str_itemize(map(srepr, obj))
        else:
            return '<empty>'
    else:
        data = unicode(obj)
        if '\n' in data:
            data = '\n' + _indent_f(data, indent)

        return data

def sprint(obj, indent=4, bullet='* '):
    '''Similar to `srepr`, but prints the result instead of returning it.'''

    print(srepr(obj, indent, bullet))

def simpleprint(*args, **kwds):
    import warnings
    warnings.warn('this method is deprecated, use sprint instead')
    return sprint(*args, **kwds)

def printiter(obj, sort=None, key=None, reverse=False, repr=repr, start='', end='', sep='\n', empty='()', final='\n', max_iter=None):
    '''Similar to `repriter`, but prints the output to stdout.'''

    if sort:
        if callable(sort):
            obj = sorted(obj, key=key, cmp=sort, reverse=reverse)
        else:
            obj = sorted(obj, key=key, reverse=reverse)

    if max_iter == 0:
        raise ValueError('maximum number of iterations cannot be null')
    elif max_iter is None:
        max_iter = float('inf')

    it = iter(obj)
    try:
        try:
            next_ = it.next()
        except StopIteration:
            sys.stdout.write(empty)
        else:
            sys.stdout.write(start)
            sys.stdout.write(repr(next_))
            sys.stdout.write(end)
            num_iters = 1
            while True:
                if num_iters >= max_iter:
                    break
                num_iters += 1
                next_ = it.next()
                sys.stdout.write(sep)
                sys.stdout.write(start)
                sys.stdout.write(repr(next_))
                sys.stdout.write(end)
    except StopIteration:
        pass
    sys.stdout.write(final)

def repriter(obj, sort=None, key=None, reverse=False, repr=repr, start='', end='', sep='\n', empty='()', final='\n', max_iter=None):
    '''Format iterator as a string.  
    
    This function can be very useful for creating specialized printers or 
    simply for debugging.
    
    Parameters
    ----------
    obj : iterable
        Iterable object to print.
    sort : bool or callable
        If True, sort the values returned by the iterator before printing. If it
        is callable, it is interpreted as the ``cmp`` argument in the sorted()
        built-in.
    key, reverse:
        Same as in built-in sorted().
    repr : callable
        Function that is called to print each element in the iterator. It 
        defaults to the repr() built-in.
    start, end, sep : str
        These strings are printed in the sequence: ``start``, ``repr(obj[i])``, 
        ``end``[, ``sep``]. ``sep`` is only printed between two objects.
    empty : str
        String that is printed if iterator is empty.
    final : str
        String that is printed after all iterations.
        
    Examples
    --------
    
    >>> print(repriter(xrange(3), start='* ', sep='\\n---\\n', final=''))
    * 0
    ---
    * 1
    ---
    * 2
    '''

    with contextlib.closing(StringIO()) as F:
        try:
            std_old, sys.stdout = sys.stdout, F
            printiter(obj, sort, key, reverse, repr, start, end, sep, empty, final, max_iter)
        finally:
            sys.stdout = std_old
        return F.getvalue()

if __name__ == '__main__':
    import doctest
    doctest.testmod(optionflags=doctest.REPORT_ONLY_FIRST_FAILURE, verbose=0)

